// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <nebula/bits/bpacking.h>
#include <nebula/version.h>
#include <nebula/bits/bpacking64_default.h>
#include <nebula/bits/bpacking_default.h>
#include <nebula/util/cpu_info.h>
#include <nebula/bits/dispatch.h>
#include <turbo/log/logging.h>

#if NEBULA_HAVE_RUNTIME_AVX2_SUPPORTED

#include <nebula/bits/bpacking_avx2.h>

#endif
#if NEBULA_HAVE_RUNTIME_AVX512_SUPPORTED
#include <nebula/bits/bpacking_avx512.h>
#endif
#if defined(NEBULA_HAVE_NEON)
#include <nebula/bits/bpacking_neon.h>
#endif

namespace nebula::internal {

    namespace {

        int unpack32_default(const uint32_t *in, uint32_t *out, int batch_size, int num_bits) {
            batch_size = batch_size / 32 * 32;
            int num_loops = batch_size / 32;

            switch (num_bits) {
                case 0:
                    for (int i = 0; i < num_loops; ++i) in = nullunpacker32(in, out + i * 32);
                    break;
                case 1:
                    for (int i = 0; i < num_loops; ++i) in = unpack1_32(in, out + i * 32);
                    break;
                case 2:
                    for (int i = 0; i < num_loops; ++i) in = unpack2_32(in, out + i * 32);
                    break;
                case 3:
                    for (int i = 0; i < num_loops; ++i) in = unpack3_32(in, out + i * 32);
                    break;
                case 4:
                    for (int i = 0; i < num_loops; ++i) in = unpack4_32(in, out + i * 32);
                    break;
                case 5:
                    for (int i = 0; i < num_loops; ++i) in = unpack5_32(in, out + i * 32);
                    break;
                case 6:
                    for (int i = 0; i < num_loops; ++i) in = unpack6_32(in, out + i * 32);
                    break;
                case 7:
                    for (int i = 0; i < num_loops; ++i) in = unpack7_32(in, out + i * 32);
                    break;
                case 8:
                    for (int i = 0; i < num_loops; ++i) in = unpack8_32(in, out + i * 32);
                    break;
                case 9:
                    for (int i = 0; i < num_loops; ++i) in = unpack9_32(in, out + i * 32);
                    break;
                case 10:
                    for (int i = 0; i < num_loops; ++i) in = unpack10_32(in, out + i * 32);
                    break;
                case 11:
                    for (int i = 0; i < num_loops; ++i) in = unpack11_32(in, out + i * 32);
                    break;
                case 12:
                    for (int i = 0; i < num_loops; ++i) in = unpack12_32(in, out + i * 32);
                    break;
                case 13:
                    for (int i = 0; i < num_loops; ++i) in = unpack13_32(in, out + i * 32);
                    break;
                case 14:
                    for (int i = 0; i < num_loops; ++i) in = unpack14_32(in, out + i * 32);
                    break;
                case 15:
                    for (int i = 0; i < num_loops; ++i) in = unpack15_32(in, out + i * 32);
                    break;
                case 16:
                    for (int i = 0; i < num_loops; ++i) in = unpack16_32(in, out + i * 32);
                    break;
                case 17:
                    for (int i = 0; i < num_loops; ++i) in = unpack17_32(in, out + i * 32);
                    break;
                case 18:
                    for (int i = 0; i < num_loops; ++i) in = unpack18_32(in, out + i * 32);
                    break;
                case 19:
                    for (int i = 0; i < num_loops; ++i) in = unpack19_32(in, out + i * 32);
                    break;
                case 20:
                    for (int i = 0; i < num_loops; ++i) in = unpack20_32(in, out + i * 32);
                    break;
                case 21:
                    for (int i = 0; i < num_loops; ++i) in = unpack21_32(in, out + i * 32);
                    break;
                case 22:
                    for (int i = 0; i < num_loops; ++i) in = unpack22_32(in, out + i * 32);
                    break;
                case 23:
                    for (int i = 0; i < num_loops; ++i) in = unpack23_32(in, out + i * 32);
                    break;
                case 24:
                    for (int i = 0; i < num_loops; ++i) in = unpack24_32(in, out + i * 32);
                    break;
                case 25:
                    for (int i = 0; i < num_loops; ++i) in = unpack25_32(in, out + i * 32);
                    break;
                case 26:
                    for (int i = 0; i < num_loops; ++i) in = unpack26_32(in, out + i * 32);
                    break;
                case 27:
                    for (int i = 0; i < num_loops; ++i) in = unpack27_32(in, out + i * 32);
                    break;
                case 28:
                    for (int i = 0; i < num_loops; ++i) in = unpack28_32(in, out + i * 32);
                    break;
                case 29:
                    for (int i = 0; i < num_loops; ++i) in = unpack29_32(in, out + i * 32);
                    break;
                case 30:
                    for (int i = 0; i < num_loops; ++i) in = unpack30_32(in, out + i * 32);
                    break;
                case 31:
                    for (int i = 0; i < num_loops; ++i) in = unpack31_32(in, out + i * 32);
                    break;
                case 32:
                    for (int i = 0; i < num_loops; ++i) in = unpack32_32(in, out + i * 32);
                    break;
                default:
                            DKCHECK(false) << "Unsupported num_bits";
            }

            return batch_size;
        }

        struct Unpack32DynamicFunction {
            using FunctionType = decltype(&unpack32_default);

            static std::vector<std::pair<DispatchLevel, FunctionType>> implementations() {
                return {{DispatchLevel::NONE, unpack32_default}
#if NEBULA_HAVE_RUNTIME_AVX2_SUPPORTED
                        ,
                        {DispatchLevel::AVX2, unpack32_avx2}
#endif
#if defined(NEBULA_HAVE_RUNTIME_AVX512)
                        ,
                        {DispatchLevel::AVX512, unpack32_avx512}
#endif
                };
            }
        };

    }  // namespace

    int unpack32(const uint32_t *in, uint32_t *out, int batch_size, int num_bits) {
#if defined(NEBULA_HAVE_NEON)
        return unpack32_neon(in, out, batch_size, num_bits);
#else
        static DynamicDispatch<Unpack32DynamicFunction> dispatch;
        return dispatch.func(in, out, batch_size, num_bits);
#endif
    }

    namespace {

        int unpack64_default(const uint8_t *in, uint64_t *out, int batch_size, int num_bits) {
            batch_size = batch_size / 32 * 32;
            int num_loops = batch_size / 32;

            switch (num_bits) {
                case 0:
                    for (int i = 0; i < num_loops; ++i) in = unpack0_64(in, out + i * 32);
                    break;
                case 1:
                    for (int i = 0; i < num_loops; ++i) in = unpack1_64(in, out + i * 32);
                    break;
                case 2:
                    for (int i = 0; i < num_loops; ++i) in = unpack2_64(in, out + i * 32);
                    break;
                case 3:
                    for (int i = 0; i < num_loops; ++i) in = unpack3_64(in, out + i * 32);
                    break;
                case 4:
                    for (int i = 0; i < num_loops; ++i) in = unpack4_64(in, out + i * 32);
                    break;
                case 5:
                    for (int i = 0; i < num_loops; ++i) in = unpack5_64(in, out + i * 32);
                    break;
                case 6:
                    for (int i = 0; i < num_loops; ++i) in = unpack6_64(in, out + i * 32);
                    break;
                case 7:
                    for (int i = 0; i < num_loops; ++i) in = unpack7_64(in, out + i * 32);
                    break;
                case 8:
                    for (int i = 0; i < num_loops; ++i) in = unpack8_64(in, out + i * 32);
                    break;
                case 9:
                    for (int i = 0; i < num_loops; ++i) in = unpack9_64(in, out + i * 32);
                    break;
                case 10:
                    for (int i = 0; i < num_loops; ++i) in = unpack10_64(in, out + i * 32);
                    break;
                case 11:
                    for (int i = 0; i < num_loops; ++i) in = unpack11_64(in, out + i * 32);
                    break;
                case 12:
                    for (int i = 0; i < num_loops; ++i) in = unpack12_64(in, out + i * 32);
                    break;
                case 13:
                    for (int i = 0; i < num_loops; ++i) in = unpack13_64(in, out + i * 32);
                    break;
                case 14:
                    for (int i = 0; i < num_loops; ++i) in = unpack14_64(in, out + i * 32);
                    break;
                case 15:
                    for (int i = 0; i < num_loops; ++i) in = unpack15_64(in, out + i * 32);
                    break;
                case 16:
                    for (int i = 0; i < num_loops; ++i) in = unpack16_64(in, out + i * 32);
                    break;
                case 17:
                    for (int i = 0; i < num_loops; ++i) in = unpack17_64(in, out + i * 32);
                    break;
                case 18:
                    for (int i = 0; i < num_loops; ++i) in = unpack18_64(in, out + i * 32);
                    break;
                case 19:
                    for (int i = 0; i < num_loops; ++i) in = unpack19_64(in, out + i * 32);
                    break;
                case 20:
                    for (int i = 0; i < num_loops; ++i) in = unpack20_64(in, out + i * 32);
                    break;
                case 21:
                    for (int i = 0; i < num_loops; ++i) in = unpack21_64(in, out + i * 32);
                    break;
                case 22:
                    for (int i = 0; i < num_loops; ++i) in = unpack22_64(in, out + i * 32);
                    break;
                case 23:
                    for (int i = 0; i < num_loops; ++i) in = unpack23_64(in, out + i * 32);
                    break;
                case 24:
                    for (int i = 0; i < num_loops; ++i) in = unpack24_64(in, out + i * 32);
                    break;
                case 25:
                    for (int i = 0; i < num_loops; ++i) in = unpack25_64(in, out + i * 32);
                    break;
                case 26:
                    for (int i = 0; i < num_loops; ++i) in = unpack26_64(in, out + i * 32);
                    break;
                case 27:
                    for (int i = 0; i < num_loops; ++i) in = unpack27_64(in, out + i * 32);
                    break;
                case 28:
                    for (int i = 0; i < num_loops; ++i) in = unpack28_64(in, out + i * 32);
                    break;
                case 29:
                    for (int i = 0; i < num_loops; ++i) in = unpack29_64(in, out + i * 32);
                    break;
                case 30:
                    for (int i = 0; i < num_loops; ++i) in = unpack30_64(in, out + i * 32);
                    break;
                case 31:
                    for (int i = 0; i < num_loops; ++i) in = unpack31_64(in, out + i * 32);
                    break;
                case 32:
                    for (int i = 0; i < num_loops; ++i) in = unpack32_64(in, out + i * 32);
                    break;
                case 33:
                    for (int i = 0; i < num_loops; ++i) in = unpack33_64(in, out + i * 32);
                    break;
                case 34:
                    for (int i = 0; i < num_loops; ++i) in = unpack34_64(in, out + i * 32);
                    break;
                case 35:
                    for (int i = 0; i < num_loops; ++i) in = unpack35_64(in, out + i * 32);
                    break;
                case 36:
                    for (int i = 0; i < num_loops; ++i) in = unpack36_64(in, out + i * 32);
                    break;
                case 37:
                    for (int i = 0; i < num_loops; ++i) in = unpack37_64(in, out + i * 32);
                    break;
                case 38:
                    for (int i = 0; i < num_loops; ++i) in = unpack38_64(in, out + i * 32);
                    break;
                case 39:
                    for (int i = 0; i < num_loops; ++i) in = unpack39_64(in, out + i * 32);
                    break;
                case 40:
                    for (int i = 0; i < num_loops; ++i) in = unpack40_64(in, out + i * 32);
                    break;
                case 41:
                    for (int i = 0; i < num_loops; ++i) in = unpack41_64(in, out + i * 32);
                    break;
                case 42:
                    for (int i = 0; i < num_loops; ++i) in = unpack42_64(in, out + i * 32);
                    break;
                case 43:
                    for (int i = 0; i < num_loops; ++i) in = unpack43_64(in, out + i * 32);
                    break;
                case 44:
                    for (int i = 0; i < num_loops; ++i) in = unpack44_64(in, out + i * 32);
                    break;
                case 45:
                    for (int i = 0; i < num_loops; ++i) in = unpack45_64(in, out + i * 32);
                    break;
                case 46:
                    for (int i = 0; i < num_loops; ++i) in = unpack46_64(in, out + i * 32);
                    break;
                case 47:
                    for (int i = 0; i < num_loops; ++i) in = unpack47_64(in, out + i * 32);
                    break;
                case 48:
                    for (int i = 0; i < num_loops; ++i) in = unpack48_64(in, out + i * 32);
                    break;
                case 49:
                    for (int i = 0; i < num_loops; ++i) in = unpack49_64(in, out + i * 32);
                    break;
                case 50:
                    for (int i = 0; i < num_loops; ++i) in = unpack50_64(in, out + i * 32);
                    break;
                case 51:
                    for (int i = 0; i < num_loops; ++i) in = unpack51_64(in, out + i * 32);
                    break;
                case 52:
                    for (int i = 0; i < num_loops; ++i) in = unpack52_64(in, out + i * 32);
                    break;
                case 53:
                    for (int i = 0; i < num_loops; ++i) in = unpack53_64(in, out + i * 32);
                    break;
                case 54:
                    for (int i = 0; i < num_loops; ++i) in = unpack54_64(in, out + i * 32);
                    break;
                case 55:
                    for (int i = 0; i < num_loops; ++i) in = unpack55_64(in, out + i * 32);
                    break;
                case 56:
                    for (int i = 0; i < num_loops; ++i) in = unpack56_64(in, out + i * 32);
                    break;
                case 57:
                    for (int i = 0; i < num_loops; ++i) in = unpack57_64(in, out + i * 32);
                    break;
                case 58:
                    for (int i = 0; i < num_loops; ++i) in = unpack58_64(in, out + i * 32);
                    break;
                case 59:
                    for (int i = 0; i < num_loops; ++i) in = unpack59_64(in, out + i * 32);
                    break;
                case 60:
                    for (int i = 0; i < num_loops; ++i) in = unpack60_64(in, out + i * 32);
                    break;
                case 61:
                    for (int i = 0; i < num_loops; ++i) in = unpack61_64(in, out + i * 32);
                    break;
                case 62:
                    for (int i = 0; i < num_loops; ++i) in = unpack62_64(in, out + i * 32);
                    break;
                case 63:
                    for (int i = 0; i < num_loops; ++i) in = unpack63_64(in, out + i * 32);
                    break;
                case 64:
                    for (int i = 0; i < num_loops; ++i) in = unpack64_64(in, out + i * 32);
                    break;
                default:
                            DKCHECK(false) << "Unsupported num_bits";
            }

            return batch_size;
        }

    }  // namespace

    int unpack64(const uint8_t *in, uint64_t *out, int batch_size, int num_bits) {
        // TODO: unpack64_neon, unpack64_avx2 and unpack64_avx512
        return unpack64_default(in, out, batch_size, num_bits);
    }

}  // namespace nebula::internal
